package com.simple.framework.web.service;

import javax.annotation.Resource;

import cn.hutool.core.bean.BeanUtil;
import com.github.zuihou.base.R;
import com.simple.business.domain.constant.SimpleConstant;
import com.simple.common.utils.bean.AuthInfo;
import com.simple.framework.security.custom.LoginFailToken;
import com.simple.framework.security.custom.LoginSuccessToken;
import com.simple.framework.security.custom.NeedBindPhoneToken;
import com.simple.framework.security.custom.ThirdAuthenticationToken;
import com.simple.business.config.utils.SimpleUtil;
import com.simple.business.domain.dto.sms.LoginDTO;
import com.simple.business.domain.entity.user.User;
import com.simple.business.domain.enumeration.ThirdAuthenticationEnum;
import com.simple.business.domain.vo.login.UserLoginSuccessVO;
import com.simple.business.service.user.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import com.simple.common.constant.Constants;
import com.simple.common.constant.UserConstants;
import com.simple.common.core.domain.model.LoginUser;
import com.simple.common.core.redis.RedisCache;
import com.simple.common.utils.MessageUtils;
import com.simple.common.utils.StringUtils;
import com.simple.common.utils.ip.IpUtils;
import com.simple.framework.manager.AsyncManager;
import com.simple.framework.manager.factory.AsyncFactory;
import com.simple.framework.security.context.AuthenticationContextHolder;

import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 登录校验方法
 * @author
 */
@Component
@Slf4j
public class SysLoginService {
    @Autowired
    private TokenService tokenService;

    @Resource
    private AuthenticationManager authenticationManager;
    @Resource
    private PasswordEncoder passwordEncoder;

    @Resource
    private RedisCache redisCache;
    @Resource
    private UserService userService;
    @Resource
    private SimpleUtil simpleUtil;


    /**
     * 登录验证  原始账号密码登录
     * @param username 用户名
     * @param password 密码
     * @param code     验证码
     * @param uuid     唯一标识
     * @return 结果
     */
    public String login(String username, String password, String code, String uuid) {
        User user = userService.getUserByUserName(username);
        // 验证码校验
        if (StringUtils.isNotEmpty(code) && !simpleUtil.validateCaptcha(username, code, uuid)) {
            return "";
        }
        // 登录前置校验
        if (!loginPreCheck(username, password, user)) {
            return "";
        }
        // 用户验证
        Authentication authentication = null;
        try {
            UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(username, password);
            AuthenticationContextHolder.setContext(authenticationToken);
            // 该方法会去调用UserDetailsServiceImpl.loadUserByUsername
            authentication = authenticationManager.authenticate(authenticationToken);
        } catch (Exception e) {
            if (e instanceof BadCredentialsException) {
                return "";
            } else {
                return "";
            }
        } finally {
            AuthenticationContextHolder.clearContext();
        }
        AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_SUCCESS, MessageUtils.message("user.login.success")));
        LoginUser loginUser = (LoginUser) authentication.getPrincipal();
        recordLoginInfo(loginUser.getUserId());

        // 重置密码错误次数
        user.setPasswordErrorNum(0);
        userService.updateById(user);

        // 生成token
        return tokenService.createToken(loginUser);
    }

    /**
     * 登录验证  第三方账号登录
     * @param loginDTO
     * @return
     */
    public R<AuthInfo> login(LoginDTO loginDTO) {
        AbstractAuthenticationToken loginToken;
        if (Objects.isNull(loginDTO.getType())) {
            loginToken = new UsernamePasswordAuthenticationToken(loginDTO.getAccount(), loginDTO.getPwd());
        } else {
            loginToken = new ThirdAuthenticationToken(loginDTO.getType(), loginDTO.getAccount(), loginDTO.getCode());
        }
        R<UserLoginSuccessVO> userLoginSuccessVOR = loginCore(loginToken, loginDTO.getType());
        if (userLoginSuccessVOR.getIsError()) {
            return R.fail("登录失败");
        }
        UserLoginSuccessVO data = userLoginSuccessVOR.getData();
        AuthInfo authInfo = new AuthInfo();
        BeanUtil.copyProperties(data, authInfo);
        authInfo.setUserId(data.getId());
        tokenService.createToken(authInfo);
        return R.success(authInfo);
    }

    /**
     * 登录实际流程
     * @param loginToken
     * @param loginType
     * @return
     */
    public R<UserLoginSuccessVO> loginCore(AbstractAuthenticationToken loginToken, ThirdAuthenticationEnum loginType){
        try {
            Authentication authenticate = authenticationManager.authenticate(loginToken);
            if (authenticate instanceof LoginSuccessToken) {
                LoginSuccessToken loginSuccessToken = (LoginSuccessToken) authenticate;
                return R.success(loginSuccessToken.getLoginSuccessVO());
            } else if (authenticate instanceof LoginFailToken) {
                LoginFailToken failToken = (LoginFailToken) authenticate;
                return R.fail("登录失败");
            }else if (authenticate instanceof NeedBindPhoneToken){
                UserLoginSuccessVO loginSuccessVO = ((NeedBindPhoneToken) authenticate).getLoginSuccessVO();
                if (loginType != null && (loginType == ThirdAuthenticationEnum.WEI_XIN_MINIPROGRAM || loginType == ThirdAuthenticationEnum.WEI_XIN_MP)) {
                    // 微信小程序归属于微信
                    loginType = ThirdAuthenticationEnum.WEI_XIN;
                }
                loginSuccessVO.setLoginType(loginType);
                // 生成idCode
                String idCode = userService.genIdCode(loginSuccessVO);
                return R.fail(SimpleConstant.USER_NEED_BIND_PHONE,idCode);
            }
        } catch (Exception exception) {
            log.error("登录失败", exception);
        }
        return R.fail(401, "登录验证失败");
    }


    /**
     * 登录前置校验
     * @param username 用户名
     * @param password 用户密码
     */
    public Boolean loginPreCheck(String username, String password, User user) {

        if (!passwordEncoder.matches(password, user.getPassword())) {
            // todo 密码错误，记录错误次数
            user.setPasswordErrorLastTime(LocalDateTime.now());
            if (user.getPasswordErrorNum() == null) {
                user.setPasswordErrorNum(0);
            }
            user.setPasswordErrorNum(user.getPasswordErrorNum() + 1);
            userService.updateById(user);
            return Boolean.FALSE;
        }

        // 用户名或密码为空 错误
        if (StringUtils.isEmpty(username) || StringUtils.isEmpty(password)) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("not.null")));
            return Boolean.FALSE;
        }
        // 密码如果不在指定范围内 错误
        if (password.length() < UserConstants.PASSWORD_MIN_LENGTH
                || password.length() > UserConstants.PASSWORD_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            return Boolean.FALSE;
        }
        // 用户名不在指定范围内 错误
        if (username.length() < UserConstants.USERNAME_MIN_LENGTH
                || username.length() > UserConstants.USERNAME_MAX_LENGTH) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("user.password.not.match")));
            return Boolean.FALSE;
        }
        // IP黑名单校验
        /*String blackStr = configService.selectConfigByKey("sys.login.blackIPList");
        if (IpUtils.isMatchedIp(blackStr, IpUtils.getIpAddr())) {
            AsyncManager.me().execute(AsyncFactory.recordLogininfor(username, Constants.LOGIN_FAIL, MessageUtils.message("login.blocked")));
            throw new BlackListException();
        }*/
        return Boolean.TRUE;
    }

    /**
     * 记录登录信息
     * @param userId 用户ID
     */
    public void recordLoginInfo(Long userId) {
        User user = new User();
        user.setId(userId);
        user.setLoginIp(IpUtils.getIpAddr());
        user.setLoginTime(LocalDateTime.now());
        userService.updateById(user);
    }
}
